﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Upreader.Usenet.Nntp.Encodings
{
    /// <summary>
    /// A readonly stream without support for seeking allowing recording and playback
    /// When requesting more bytes in playback mode than the underlying record buffer holds,
    /// The original baseStream will be used for additional bytes
    /// </summary>
    public class RecordStream : Stream
    {
        #region Fields

        private readonly Stream _baseStream;

        private bool _isDisposed;
        
        // by default, record in a underlying memory stream
        private MemoryStream _recordingStream;
        private RecordStreamState _recordState;

        #endregion // Fields

        #region Constructor

        public RecordStream(Stream baseStream)
        {
            if (baseStream == null)
                throw new ArgumentNullException("baseStream");

            _baseStream = baseStream;
            _recordState = RecordStreamState.None;
        }

        #endregion // Constructor

        #region Protected properties

        protected bool IsDisposed { get { return _isDisposed; } }

        #endregion // Protected properties

        #region Properties

        public Stream BaseStream { get { return _baseStream; } }

        public RecordStreamState RecordState { get { return _recordState;} }

        /// <summary>
        /// Dependent on the BaseStream
        /// </summary>
        public override bool CanRead
        {
            get { return BaseStream.CanRead; }
        }

        /// <summary>
        /// Not supported, always false
        /// </summary>
        public override bool CanSeek
        {
            get { return false; }
        }

        /// <summary>
        /// Not supported, always false
        /// </summary>
        public override bool CanWrite
        {
            get { return false; }
        }

        /// <summary>
        /// Not supported, throws
        /// </summary>
        public override long Length
        {
            get { throw new NotSupportedException(); }
        }

        /// <summary>
        /// Not supported, throws
        /// </summary>
        public override long Position
        {
            get
            {
                throw new NotSupportedException();
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        #endregion // Properties

        #region Methods

        public override void Flush()
        {
            ValidateObjectState();
            BaseStream.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            ValidateObjectState();

            int total;

            switch (_recordState)
            {
                case RecordStreamState.None:
                    total = ReadBaseStream(buffer, offset, count);
                    break;
                case RecordStreamState.Playback:
                case RecordStreamState.OnlyPlayback:
                    total = ReadRecordedStream(buffer, offset, count);
                    if (total < count)
                    {
                        // set the state to recording or none since were done playing back
                        if (_recordState == RecordStreamState.Playback)
                        {
                            _recordState = RecordStreamState.Recording;
                            if (_recordingStream == null)
                                _recordingStream = new MemoryStream();
                        }
                        else // OnlyPlayback
                            _recordState = RecordStreamState.None;

                        int relativeOffset = offset + total;
                        int relativeCount = count - total;
                        
                        // we need to read more data (since we changed the state, we can savely go recursive)
                        total += Read(buffer, relativeOffset, relativeCount);
                    }
                    break;
                case RecordStreamState.Recording:
                    total = ReadBaseStream(buffer, offset, count);
                    // and store it temporarily in the buffer
                    _recordingStream.Write(buffer, offset, total);
                    if (_recordingStream.Length > 1000)
                    {


                    }
                    break;
                default:
                    throw new InvalidOperationException("Invalid state");
            }

            return total;
        }

        /// <summary>
        /// not supported, always throws
        /// </summary>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not supported, throws
        /// </summary>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// not supported, always throws
        /// </summary>
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Closes the current and underlying stream
        /// </summary>
        public override void Close()
        {
            ValidateObjectState();
            
            BaseStream.Close();
            ClearCurrentRecordingBuffer();
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            BaseStream.Dispose();
            
            if (disposing)
            {
                ClearCurrentRecordingBuffer();
            }

            _isDisposed = true;
        }

        /// <summary>
        /// Starts a new record clearing everything in the current pending buffer
        /// </summary>
        public void Record()
        {
            if (RecordState == RecordStreamState.Recording)
                throw new InvalidOperationException(Resource.RecordStream_Recording);

            ClearCurrentRecordingBuffer();

            _recordingStream = new MemoryStream();
            _recordState = RecordStreamState.Recording;
        }

        public void Playback()
        {
            Playback(true);
        }

        public void Playback(bool autoRecord)
        {
            if (_recordingStream != null)
                _recordingStream.Position = 0;

            if (autoRecord)
                _recordState = RecordStreamState.Playback;
            else
                _recordState = RecordStreamState.OnlyPlayback;
        }

        #endregion // Methods

        #region Helpers

        private int ReadBaseStream(byte[] buffer, int offset, int count)
        {
            return BaseStream.Read(buffer, offset, count);
        }

        private int ReadRecordedStream(byte[] buffer, int offset, int count)
        {
            if (_recordingStream == null)
                return 0;
            else
                return _recordingStream.Read(buffer, offset, count);
        }

        private void ClearCurrentRecordingBuffer()
        {
            if (_recordingStream != null)
                _recordingStream.Dispose();
        }

        private void ValidateObjectState()
        {
            if (_isDisposed)
                throw new InvalidOperationException(Resource.ObjectDisposed);
        }

        #endregion // Helpers
    }
}
